package com.fjsh.config;

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitConfig 概念介绍：
 * 1.Queue:队列，是RabbitMq的内部对象，用于存储消息，RabbitMq的多个消费者可以订阅同一个队列，此时队列会以轮询的方式给多个消费者消费，而非多个消费者都收到所有的消息进行消费
 * 注意：RabbitMQ不支持队列层面的广播消费，如果需要广播消费，可以采用一个交换器通过路由Key绑定多个队列，由多个消费者来订阅这些队列的方式。
 * 2.Exchange:交换器，在RabbitMq中，生产者并非直接将消息投递到队列中。真实情况是，生产者将消息发送到Exchange(交换器),由交换器将消息路由到一个或多个队列中。
 * 注意：如果路由不到，或返回给生产者，或直接丢弃，或做其它处理。
 * 3.RoutingKey:路由Key,生产者将消息发送给交换器的时候，一般会指定一个RoutingKey，用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能
 * 最终生效。在交换器类型和绑定键固定的情况下，生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪个队列。
 * 4.Binding：RabbitMQ通过绑定将交换器和队列关联起来，在绑定的时候一般会指定一个绑定键，这样RabbitMQ就可以指定如何正确的路由到队列了。
 */
@Configuration
public class RabbitConfig {

    //region 属性注入
    /**
     * RabbitMq的主机地址
     */
    @Value("${spring.rabbitmq.host}")
    private String host;

    /**
     * RabbitMq的端口
     */
    @Value("${spring.rabbitmq.port}")
    private Integer port;

    /**
     * 用户账号
     */
    @Value("${spring.rabbitmq.username}")
    private String username;

    /**
     * 用户密码
     */
    @Value("${spring.rabbitmq.password}")
    private String password;

    /**
     * 消息确认，回调机制
     */
    @Value("${spring.rabbitmq.publisher-confirms}")
    private boolean confirms;
    @Value("${spring.rabbitmq.publisher-returns}")
    private boolean returns;

    /**
     * vhost:test
     */
    @Value("${spring.rabbitmq.test-virtual-host}")
    private String hrmTestVirtualHost;
    //endregion

    //region RabbitTemplate配置
    /**
     * 若一个项目只使用一个virtualHost的话，默认只需要在配置文件中配置其属性即可
     * 若项目中使用到多个virtualHost，那么可以以通过创建ConnectionFactory的方式指定不同的virtualHost
     * @return
     */
    public CachingConnectionFactory createConnectionFactory(String host, Integer port, String username, String password,String vHost) {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setSimplePublisherConfirms(confirms);
        connectionFactory.setPublisherReturns(returns);
        connectionFactory.setVirtualHost(vHost);
        return connectionFactory;
    }

    /**
     * 创建指定vhost:test的连接工厂
     */
    @Bean(name = "testConnectionFactory")
    public ConnectionFactory testConnectionFactory() {
        return createConnectionFactory(host, port, username, password, hrmTestVirtualHost);
    }

    /**
     * 若有多个vhost则自定义RabbitMqTemplate 通过名称指定对应的vhost
     */
    @Bean(name = "testRabbitTemplate")
    public RabbitTemplate testRabbitTemplate(@Qualifier(value = "testConnectionFactory") ConnectionFactory connectionFactory) {

        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 消息确认机制，ConnectionFactory中必须设置回调机制（publisher-confirms,publisher-returns)
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack){
                // 此处可记录日志，通知消息已被成功消费
                String messageId = correlationData.getId();
                System.out.println("confirm:"+messageId);
            }else {
                // 未被消费则根据业务需要处理
                System.out.println("消息id为: 【" + correlationData + "】的消息发送失败，失败原因是：" + cause);
            }
        });

        // 消息返回机制,如果发送到交换器成功，但是没有匹配的队列，就会触发这个回调
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            System.out.println("消息主体 message : " + message);
            System.out.println("消息主体 message : " + replyCode);
            System.out.println("描述：" + replyText);
            System.out.println("消息使用的交换器 exchange : " + exchange);
            System.out.println("消息使用的路由键 routing : " + routingKey);
        });

        return rabbitTemplate;
    }
    //endregion

}
